// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================

package org.eclipse.jetty.util.ajax;

import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * JSON Parser and Generator.
 * <p>
 * This class provides some static methods to convert POJOs to and from JSON notation. The mapping from JSON to java is:
 * 
 * <pre>
 *   object ==> Map
 *   array  ==> Object[]
 *   number ==> Double or Long
 *   string ==> String
 *   null   ==> null
 *   bool   ==> Boolean
 * </pre>
 * 
 * </p>
 * <p>
 * The java to JSON mapping is:
 * 
 * <pre>
 *   String --> string
 *   Number --> number
 *   Map    --> object
 *   List   --> array
 *   Array  --> array
 *   null   --> null
 *   Boolean--> boolean
 *   Object --> string (dubious!)
 * </pre>
 * 
 * </p>
 * <p>
 * The interface {@link JSON.Convertible} may be implemented by classes that wish to externalize and initialize specific fields to and from JSON objects. Only directed acyclic graphs of objects are supported.
 * </p>
 * <p>
 * The interface {@link JSON.Generator} may be implemented by classes that know how to render themselves as JSON and the {@link #toString(Object)} method will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON. The class {@link JSON.Literal} may be used to hold pre-generated JSON object.
 * <p>
 * The interface {@link JSON.Convertor} may be implemented to provide static convertors for objects that may be registered with {@link #registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)} . These convertors are looked up by class, interface and super class by {@link #getConvertor(Class)}.
 * </p>
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class JSON
{

	private static final Logger LOG = Log.getLogger(JSON.class);
	public final static JSON DEFAULT = new JSON();

	private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>();
	private int _stringBufferSize = 1024;

	public JSON()
	{}

	/* ------------------------------------------------------------ */
	/**
	 * @return the initial stringBuffer size to use when creating JSON strings (default 1024)
	 */
	public int getStringBufferSize()
	{
		return _stringBufferSize;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param stringBufferSize the initial stringBuffer size to use when creating JSON strings (default 1024)
	 */
	public void setStringBufferSize(int stringBufferSize)
	{
		_stringBufferSize = stringBufferSize;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Register a {@link Convertor} for a class or interface.
	 * 
	 * @param forClass The class or interface that the convertor applies to
	 * @param convertor the convertor
	 */
	public static void registerConvertor(Class forClass, Convertor convertor)
	{
		DEFAULT.addConvertor(forClass, convertor);
	}

	/* ------------------------------------------------------------ */
	public static JSON getDefault()
	{
		return DEFAULT;
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public static void setDefault(JSON json)
	{}

	/* ------------------------------------------------------------ */
	public static String toString(Object object)
	{
		StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
		DEFAULT.append(buffer, object);
		return buffer.toString();
	}

	/* ------------------------------------------------------------ */
	public static String toString(Map object)
	{
		StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
		DEFAULT.appendMap(buffer, object);
		return buffer.toString();
	}

	/* ------------------------------------------------------------ */
	public static String toString(Object[] array)
	{
		StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
		DEFAULT.appendArray(buffer, array);
		return buffer.toString();
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param s String containing JSON object or array.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	public static Object parse(String s)
	{
		return DEFAULT.parse(new StringSource(s), false);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param s String containing JSON object or array.
	 * @param stripOuterComment If true, an outer comment around the JSON is ignored.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	public static Object parse(String s, boolean stripOuterComment)
	{
		return DEFAULT.parse(new StringSource(s), stripOuterComment);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param in Reader containing JSON object or array.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	public static Object parse(Reader in) throws IOException
	{
		return DEFAULT.parse(new ReaderSource(in), false);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @param in Reader containing JSON object or array.
	 * @param stripOuterComment If true, an outer comment around the JSON is ignored.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	public static Object parse(Reader in, boolean stripOuterComment) throws IOException
	{
		return DEFAULT.parse(new ReaderSource(in), stripOuterComment);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @deprecated use {@link #parse(Reader)}
	 * @param in Reader containing JSON object or array.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	@Deprecated
	public static Object parse(InputStream in) throws IOException
	{
		return DEFAULT.parse(new StringSource(IO.toString(in)), false);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @deprecated use {@link #parse(Reader, boolean)}
	 * @param in Stream containing JSON object or array.
	 * @param stripOuterComment If true, an outer comment around the JSON is ignored.
	 * @return A Map, Object array or primitive array parsed from the JSON.
	 */
	@Deprecated
	public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
	{
		return DEFAULT.parse(new StringSource(IO.toString(in)), stripOuterComment);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Convert Object to JSON
	 * 
	 * @param object The object to convert
	 * @return The JSON String
	 */
	public String toJSON(Object object)
	{
		StringBuilder buffer = new StringBuilder(getStringBufferSize());
		append(buffer, object);
		return buffer.toString();
	}

	/* ------------------------------------------------------------ */
	/**
	 * Convert JSON to Object
	 * 
	 * @param json The json to convert
	 * @return The object
	 */
	public Object fromJSON(String json)
	{
		Source source = new StringSource(json);
		return parse(source);
	}

	@Deprecated
	public void append(StringBuffer buffer, Object object)
	{
		append((Appendable)buffer, object);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Append object as JSON to string buffer.
	 * 
	 * @param buffer the buffer to append to
	 * @param object the object to append
	 */
	public void append(Appendable buffer, Object object)
	{
		try
		{
			if (object == null)
				buffer.append("null");
			else if (object instanceof Convertible)
				appendJSON(buffer, (Convertible)object);
			else if (object instanceof Generator)
				appendJSON(buffer, (Generator)object);
			else if (object instanceof Map)
				appendMap(buffer, (Map)object);
			else if (object instanceof Collection)
				appendArray(buffer, (Collection)object);
			else if (object.getClass().isArray())
				appendArray(buffer, object);
			else if (object instanceof Number)
				appendNumber(buffer, (Number)object);
			else if (object instanceof Boolean)
				appendBoolean(buffer, (Boolean)object);
			else if (object instanceof Character)
				appendString(buffer, object.toString());
			else if (object instanceof String)
				appendString(buffer, (String)object);
			else
			{
				Convertor convertor = getConvertor(object.getClass());
				if (convertor != null)
					appendJSON(buffer, convertor, object);
				else
					appendString(buffer, object.toString());
			}
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendNull(StringBuffer buffer)
	{
		appendNull((Appendable)buffer);
	}

	/* ------------------------------------------------------------ */
	public void appendNull(Appendable buffer)
	{
		try
		{
			buffer.append("null");
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
	{
		appendJSON((Appendable)buffer, convertor, object);
	}

	/* ------------------------------------------------------------ */
	public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
	{
		appendJSON(buffer, new Convertible()
		{

			public void fromJSON(Map object)
			{}

			public void toJSON(Output out)
			{
				convertor.toJSON(object, out);
			}
		});
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendJSON(final StringBuffer buffer, Convertible converter)
	{
		appendJSON((StringBuffer)buffer, converter);
	}

	/* ------------------------------------------------------------ */
	public void appendJSON(final Appendable buffer, Convertible converter)
	{
		ConvertableOutput out = new ConvertableOutput(buffer);
		converter.toJSON(out);
		out.complete();
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendJSON(StringBuffer buffer, Generator generator)
	{
		generator.addJSON(buffer);
	}

	/* ------------------------------------------------------------ */
	public void appendJSON(Appendable buffer, Generator generator)
	{
		generator.addJSON(buffer);
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendMap(StringBuffer buffer, Map<?, ?> map)
	{
		appendMap((Appendable)buffer, map);
	}

	/* ------------------------------------------------------------ */
	public void appendMap(Appendable buffer, Map<?, ?> map)
	{
		try
		{
			if (map == null)
			{
				appendNull(buffer);
				return;
			}

			buffer.append('{');
			Iterator<?> iter = map.entrySet().iterator();
			while (iter.hasNext())
			{
				Map.Entry<?, ?> entry = (Map.Entry<?, ?>)iter.next();
				QuotedStringTokenizer.quote(buffer, entry.getKey().toString());
				buffer.append(':');
				append(buffer, entry.getValue());
				if (iter.hasNext())
					buffer.append(',');
			}

			buffer.append('}');
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendArray(StringBuffer buffer, Collection collection)
	{
		appendArray((Appendable)buffer, collection);
	}

	/* ------------------------------------------------------------ */
	public void appendArray(Appendable buffer, Collection collection)
	{
		try
		{
			if (collection == null)
			{
				appendNull(buffer);
				return;
			}

			buffer.append('[');
			Iterator iter = collection.iterator();
			boolean first = true;
			while (iter.hasNext())
			{
				if (!first)
					buffer.append(',');

				first = false;
				append(buffer, iter.next());
			}

			buffer.append(']');
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendArray(StringBuffer buffer, Object array)
	{
		appendArray((Appendable)buffer, array);
	}

	/* ------------------------------------------------------------ */
	public void appendArray(Appendable buffer, Object array)
	{
		try
		{
			if (array == null)
			{
				appendNull(buffer);
				return;
			}

			buffer.append('[');
			int length = Array.getLength(array);

			for (int i = 0; i < length; i++)
			{
				if (i != 0)
					buffer.append(',');
				append(buffer, Array.get(array, i));
			}

			buffer.append(']');
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendBoolean(StringBuffer buffer, Boolean b)
	{
		appendBoolean((Appendable)buffer, b);
	}

	/* ------------------------------------------------------------ */
	public void appendBoolean(Appendable buffer, Boolean b)
	{
		try
		{
			if (b == null)
			{
				appendNull(buffer);
				return;
			}
			buffer.append(b.booleanValue() ? "true" : "false");
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendNumber(StringBuffer buffer, Number number)
	{
		appendNumber((Appendable)buffer, number);
	}

	/* ------------------------------------------------------------ */
	public void appendNumber(Appendable buffer, Number number)
	{
		try
		{
			if (number == null)
			{
				appendNull(buffer);
				return;
			}
			buffer.append(String.valueOf(number));
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}

	/* ------------------------------------------------------------ */
	@Deprecated
	public void appendString(StringBuffer buffer, String string)
	{
		appendString((Appendable)buffer, string);
	}

	/* ------------------------------------------------------------ */
	public void appendString(Appendable buffer, String string)
	{
		if (string == null)
		{
			appendNull(buffer);
			return;
		}

		QuotedStringTokenizer.quote(buffer, string);
	}

	// Parsing utilities

	/* ------------------------------------------------------------ */
	protected String toString(char[] buffer, int offset, int length)
	{
		return new String(buffer, offset, length);
	}

	/* ------------------------------------------------------------ */
	protected Map<String, Object> newMap()
	{
		return new HashMap<String, Object>();
	}

	/* ------------------------------------------------------------ */
	protected Object[] newArray(int size)
	{
		return new Object[size];
	}

	/* ------------------------------------------------------------ */
	protected JSON contextForArray()
	{
		return this;
	}

	/* ------------------------------------------------------------ */
	protected JSON contextFor(String field)
	{
		return this;
	}

	/* ------------------------------------------------------------ */
	protected Object convertTo(Class type, Map map)
	{
		if (type != null && Convertible.class.isAssignableFrom(type))
		{
			try
			{
				Convertible conv = (Convertible)type.newInstance();
				conv.fromJSON(map);
				return conv;
			} catch (Exception e)
			{
				throw new RuntimeException(e);
			}
		}

		Convertor convertor = getConvertor(type);
		if (convertor != null)
		{
			return convertor.fromJSON(map);
		}
		return map;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Register a {@link Convertor} for a class or interface.
	 * 
	 * @param forClass The class or interface that the convertor applies to
	 * @param convertor the convertor
	 */
	public void addConvertor(Class forClass, Convertor convertor)
	{
		_convertors.put(forClass.getName(), convertor);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Lookup a convertor for a class.
	 * <p>
	 * If no match is found for the class, then the interfaces for the class are tried. If still no match is found, then the super class and it's interfaces are tried recursively.
	 * 
	 * @param forClass The class
	 * @return a {@link JSON.Convertor} or null if none were found.
	 */
	@SuppressWarnings("null")
	protected Convertor getConvertor(Class forClass)
	{
		Class cls = forClass;
		Convertor convertor = _convertors.get(cls.getName());
		if (convertor == null && this != DEFAULT)
			convertor = DEFAULT.getConvertor(cls);

		while (convertor == null && cls != null && cls != Object.class)
		{
			Class[] ifs = cls.getInterfaces();
			int i = 0;
			while (convertor == null && ifs != null && i < ifs.length)
				convertor = _convertors.get(ifs[i++].getName());
			if (convertor == null)
			{
				cls = cls.getSuperclass();
				convertor = _convertors.get(cls.getName());
			}
		}
		return convertor;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Register a {@link JSON.Convertor} for a named class or interface.
	 * 
	 * @param name name of a class or an interface that the convertor applies to
	 * @param convertor the convertor
	 */
	public void addConvertorFor(String name, Convertor convertor)
	{
		_convertors.put(name, convertor);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Lookup a convertor for a named class.
	 * 
	 * @param name name of the class
	 * @return a {@link JSON.Convertor} or null if none were found.
	 */
	public Convertor getConvertorFor(String name)
	{
		String clsName = name;
		Convertor convertor = _convertors.get(clsName);
		if (convertor == null && this != DEFAULT)
			convertor = DEFAULT.getConvertorFor(clsName);
		return convertor;
	}

	/* ------------------------------------------------------------ */
	public Object parse(Source source, boolean stripOuterComment)
	{
		int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
		if (!stripOuterComment)
			return parse(source);

		int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */

		Object o = null;
		while (source.hasNext())
		{
			char c = source.peek();

			// handle // or /* comment
			if (comment_state == 1)
			{
				switch (c)
				{
					case '/':
						comment_state = -1;
						break;
					case '*':
						comment_state = 2;
						if (strip_state == 1)
						{
							comment_state = 0;
							strip_state = 2;
						}
				}
			}
			// handle /* */ comment
			else if (comment_state > 1)
			{
				switch (c)
				{
					case '*':
						comment_state = 3;
						break;
					case '/':
						if (comment_state == 3)
						{
							comment_state = 0;
							if (strip_state == 2)
								return o;
						}
						else
							comment_state = 2;
						break;
					default:
						comment_state = 2;
				}
			}
			// handle // comment
			else if (comment_state < 0)
			{
				switch (c)
				{
					case '\r':
					case '\n':
						comment_state = 0;
					default:
						break;
				}
			}
			// handle unknown
			else
			{
				if (!Character.isWhitespace(c))
				{
					if (c == '/')
						comment_state = 1;
					else if (c == '*')
						comment_state = 3;
					else if (o == null)
					{
						o = parse(source);
						continue;
					}
				}
			}

			source.next();
		}

		return o;
	}

	/* ------------------------------------------------------------ */
	public Object parse(Source source)
	{
		int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"

		while (source.hasNext())
		{
			char c = source.peek();

			// handle // or /* comment
			if (comment_state == 1)
			{
				switch (c)
				{
					case '/':
						comment_state = -1;
						break;
					case '*':
						comment_state = 2;
				}
			}
			// handle /* */ comment
			else if (comment_state > 1)
			{
				switch (c)
				{
					case '*':
						comment_state = 3;
						break;
					case '/':
						if (comment_state == 3)
							comment_state = 0;
						else
							comment_state = 2;
						break;
					default:
						comment_state = 2;
				}
			}
			// handle // comment
			else if (comment_state < 0)
			{
				switch (c)
				{
					case '\r':
					case '\n':
						comment_state = 0;
						break;
					default:
						break;
				}
			}
			// handle unknown
			else
			{
				switch (c)
				{
					case '{':
						return parseObject(source);
					case '[':
						return parseArray(source);
					case '"':
						return parseString(source);
					case '-':
						return parseNumber(source);

					case 'n':
						complete("null", source);
						return null;
					case 't':
						complete("true", source);
						return Boolean.TRUE;
					case 'f':
						complete("false", source);
						return Boolean.FALSE;
					case 'u':
						complete("undefined", source);
						return null;
					case 'N':
						complete("NaN", source);
						return null;

					case '/':
						comment_state = 1;
						break;

					default:
						if (Character.isDigit(c))
							return parseNumber(source);
						else if (Character.isWhitespace(c))
							break;
						return handleUnknown(source, c);
				}
			}
			source.next();
		}

		return null;
	}

	/* ------------------------------------------------------------ */
	protected Object handleUnknown(Source source, char c)
	{
		throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
	}

	/* ------------------------------------------------------------ */
	protected Object parseObject(Source source)
	{
		if (source.next() != '{')
			throw new IllegalStateException();
		Map<String, Object> map = newMap();

		char next = seekTo("\"}", source);

		while (source.hasNext())
		{
			if (next == '}')
			{
				source.next();
				break;
			}

			String name = parseString(source);
			seekTo(':', source);
			source.next();

			Object value = contextFor(name).parse(source);
			map.put(name, value);

			seekTo(",}", source);
			next = source.next();
			if (next == '}')
				break;
			else
				next = seekTo("\"}", source);
		}

		String classname = (String)map.get("class");
		if (classname != null)
		{
			try
			{
				Class c = Loader.loadClass(JSON.class, classname);
				return convertTo(c, map);
			} catch (ClassNotFoundException e)
			{
				e.printStackTrace();
			}
		}
		return map;
	}

	/* ------------------------------------------------------------ */
	@SuppressWarnings("null")
	protected Object parseArray(Source source)
	{
		if (source.next() != '[')
			throw new IllegalStateException();

		int size = 0;
		ArrayList list = null;
		Object item = null;
		boolean coma = true;

		while (source.hasNext())
		{
			char c = source.peek();
			switch (c)
			{
				case ']':
					source.next();
					switch (size)
					{
						case 0:
							return newArray(0);
						case 1:
							Object array = newArray(1);
							Array.set(array, 0, item);
							return array;
						default:
							return list.toArray(newArray(list.size()));
					}

				case ',':
					if (coma)
						throw new IllegalStateException();
					coma = true;
					source.next();
					break;

				default:
					if (Character.isWhitespace(c))
						source.next();
					else
					{
						coma = false;
						if (size++ == 0)
							item = contextForArray().parse(source);
						else if (list == null)
						{
							list = new ArrayList();
							list.add(item);
							item = contextForArray().parse(source);
							list.add(item);
							item = null;
						}
						else
						{
							item = contextForArray().parse(source);
							list.add(item);
							item = null;
						}
					}
			}

		}

		throw new IllegalStateException("unexpected end of array");
	}

	/* ------------------------------------------------------------ */
	protected String parseString(Source source)
	{
		if (source.next() != '"')
			throw new IllegalStateException();

		boolean escape = false;

		StringBuilder b = null;
		final char[] scratch = source.scratchBuffer();

		if (scratch != null)
		{
			int i = 0;
			while (source.hasNext())
			{
				if (i >= scratch.length)
				{
					// we have filled the scratch buffer, so we must
					// use the StringBuffer for a large string
					b = new StringBuilder(scratch.length * 2);
					b.append(scratch, 0, i);
					break;
				}

				char c = source.next();

				if (escape)
				{
					escape = false;
					switch (c)
					{
						case '"':
							scratch[i++] = '"';
							break;
						case '\\':
							scratch[i++] = '\\';
							break;
						case '/':
							scratch[i++] = '/';
							break;
						case 'b':
							scratch[i++] = '\b';
							break;
						case 'f':
							scratch[i++] = '\f';
							break;
						case 'n':
							scratch[i++] = '\n';
							break;
						case 'r':
							scratch[i++] = '\r';
							break;
						case 't':
							scratch[i++] = '\t';
							break;
						case 'u':
							char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
								+ (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
							scratch[i++] = uc;
							break;
						default:
							scratch[i++] = c;
					}
				}
				else if (c == '\\')
				{
					escape = true;
					continue;
				}
				else if (c == '\"')
				{
					// Return string that fits within scratch buffer
					return toString(scratch, 0, i);
				}
				else
					scratch[i++] = c;
			}

			// Missing end quote, but return string anyway ?
			if (b == null)
				return toString(scratch, 0, i);
		}
		else
			b = new StringBuilder(getStringBufferSize());

		// parse large string into string buffer
		final StringBuilder builder = b;
		while (source.hasNext())
		{
			char c = source.next();

			if (escape)
			{
				escape = false;
				switch (c)
				{
					case '"':
						builder.append('"');
						break;
					case '\\':
						builder.append('\\');
						break;
					case '/':
						builder.append('/');
						break;
					case 'b':
						builder.append('\b');
						break;
					case 'f':
						builder.append('\f');
						break;
					case 'n':
						builder.append('\n');
						break;
					case 'r':
						builder.append('\r');
						break;
					case 't':
						builder.append('\t');
						break;
					case 'u':
						char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
							+ (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
						builder.append(uc);
						break;
					default:
						builder.append(c);
				}
			}
			else if (c == '\\')
			{
				escape = true;
				continue;
			}
			else if (c == '\"')
				break;
			else
				builder.append(c);
		}
		return builder.toString();
	}

	/* ------------------------------------------------------------ */
	public Number parseNumber(Source source)
	{
		boolean minus = false;
		long number = 0;
		StringBuilder buffer = null;

		longLoop: while (source.hasNext())
		{
			char c = source.peek();
			switch (c)
			{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					number = number * 10 + (c - '0');
					source.next();
					break;

				case '-':
				case '+':
					if (number != 0)
						throw new IllegalStateException("bad number");
					minus = true;
					source.next();
					break;

				case '.':
				case 'e':
				case 'E':
					buffer = new StringBuilder(16);
					if (minus)
						buffer.append('-');
					buffer.append(number);
					buffer.append(c);
					source.next();
					break longLoop;

				default:
					break longLoop;
			}
		}

		if (buffer == null)
			return minus ? -1 * number : number;

		doubleLoop: while (source.hasNext())
		{
			char c = source.peek();
			switch (c)
			{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case '-':
				case '.':
				case '+':
				case 'e':
				case 'E':
					buffer.append(c);
					source.next();
					break;

				default:
					break doubleLoop;
			}
		}
		return new Double(buffer.toString());

	}

	/* ------------------------------------------------------------ */
	protected void seekTo(char seek, Source source)
	{
		while (source.hasNext())
		{
			char c = source.peek();
			if (c == seek)
				return;

			if (!Character.isWhitespace(c))
				throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
			source.next();
		}

		throw new IllegalStateException("Expected '" + seek + "'");
	}

	/* ------------------------------------------------------------ */
	protected char seekTo(String seek, Source source)
	{
		while (source.hasNext())
		{
			char c = source.peek();
			if (seek.indexOf(c) >= 0)
			{
				return c;
			}

			if (!Character.isWhitespace(c))
				throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
			source.next();
		}

		throw new IllegalStateException("Expected one of '" + seek + "'");
	}

	/* ------------------------------------------------------------ */
	protected static void complete(String seek, Source source)
	{
		int i = 0;
		while (source.hasNext() && i < seek.length())
		{
			char c = source.next();
			if (c != seek.charAt(i++))
				throw new IllegalStateException("Unexpected '" + c + " while seeking  \"" + seek + "\"");
		}

		if (i < seek.length())
			throw new IllegalStateException("Expected \"" + seek + "\"");
	}

	private final class ConvertableOutput implements Output
	{

		private final Appendable _buffer;
		char c = '{';

		private ConvertableOutput(Appendable buffer)
		{
			_buffer = buffer;
		}

		public void complete()
		{
			try
			{
				if (c == '{')
					_buffer.append("{}");
				else if (c != 0)
					_buffer.append("}");
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}

		public void add(Object obj)
		{
			if (c == 0)
				throw new IllegalStateException();
			append(_buffer, obj);
			c = 0;
		}

		public void addClass(Class type)
		{
			try
			{
				if (c == 0)
					throw new IllegalStateException();
				_buffer.append(c);
				_buffer.append("\"class\":");
				append(_buffer, type.getName());
				c = ',';
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}

		public void add(String name, Object value)
		{
			try
			{
				if (c == 0)
					throw new IllegalStateException();
				_buffer.append(c);
				QuotedStringTokenizer.quote(_buffer, name);
				_buffer.append(':');
				append(_buffer, value);
				c = ',';
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}

		public void add(String name, double value)
		{
			try
			{
				if (c == 0)
					throw new IllegalStateException();
				_buffer.append(c);
				QuotedStringTokenizer.quote(_buffer, name);
				_buffer.append(':');
				appendNumber(_buffer, new Double(value));
				c = ',';
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}

		public void add(String name, long value)
		{
			try
			{
				if (c == 0)
					throw new IllegalStateException();
				_buffer.append(c);
				QuotedStringTokenizer.quote(_buffer, name);
				_buffer.append(':');
				appendNumber(_buffer, value);
				c = ',';
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}

		public void add(String name, boolean value)
		{
			try
			{
				if (c == 0)
					throw new IllegalStateException();
				_buffer.append(c);
				QuotedStringTokenizer.quote(_buffer, name);
				_buffer.append(':');
				appendBoolean(_buffer, value ? Boolean.TRUE : Boolean.FALSE);
				c = ',';
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}
	}

	/* ------------------------------------------------------------ */
	public interface Source
	{

		boolean hasNext();

		char next();

		char peek();

		char[] scratchBuffer();
	}

	/* ------------------------------------------------------------ */
	public static class StringSource implements Source
	{

		private final String string;
		private int index;
		private char[] scratch;

		public StringSource(String s)
		{
			string = s;
		}

		public boolean hasNext()
		{
			if (index < string.length())
				return true;
			scratch = null;
			return false;
		}

		public char next()
		{
			return string.charAt(index++);
		}

		public char peek()
		{
			return string.charAt(index);
		}

		@Override
		public String toString()
		{
			return string.substring(0, index) + "|||" + string.substring(index);
		}

		public char[] scratchBuffer()
		{
			if (scratch == null)
				scratch = new char[string.length()];
			return scratch;
		}
	}

	/* ------------------------------------------------------------ */
	public static class ReaderSource implements Source
	{

		private Reader _reader;
		private int _next = -1;
		private char[] scratch;

		public ReaderSource(Reader r)
		{
			_reader = r;
		}

		public void setReader(Reader reader)
		{
			_reader = reader;
			_next = -1;
		}

		public boolean hasNext()
		{
			getNext();
			if (_next < 0)
			{
				scratch = null;
				return false;
			}
			return true;
		}

		public char next()
		{
			getNext();
			char c = (char)_next;
			_next = -1;
			return c;
		}

		public char peek()
		{
			getNext();
			return (char)_next;
		}

		private void getNext()
		{
			if (_next < 0)
			{
				try
				{
					_next = _reader.read();
				} catch (IOException e)
				{
					throw new RuntimeException(e);
				}
			}
		}

		public char[] scratchBuffer()
		{
			if (scratch == null)
				scratch = new char[1024];
			return scratch;
		}

	}

	/* ------------------------------------------------------------ */
	/**
	 * JSON Output class for use by {@link Convertible}.
	 */
	public interface Output
	{

		public void addClass(Class c);

		public void add(Object obj);

		public void add(String name, Object value);

		public void add(String name, double value);

		public void add(String name, long value);

		public void add(String name, boolean value);
	}

	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	/**
	 * JSON Convertible object. Object can implement this interface in a similar way to the {@link Externalizable} interface is used to allow classes to provide their own serialization mechanism.
	 * <p>
	 * A JSON.Convertible object may be written to a JSONObject or initialized from a Map of field names to values.
	 * <p>
	 * If the JSON is to be convertible back to an Object, then the method {@link Output#addClass(Class)} must be called from within toJSON()
	 */
	public interface Convertible
	{

		public void toJSON(Output out);

		public void fromJSON(Map object);
	}

	/* ------------------------------------------------------------ */
	/**
	 * Static JSON Convertor.
	 * <p>
	 * may be implemented to provide static convertors for objects that may be registered with {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)} . These convertors are looked up by class, interface and super class by {@link JSON#getConvertor(Class)}. Convertors should be used when the classes to be converted cannot implement {@link Convertible} or {@link Generator}.
	 */
	public interface Convertor
	{

		public void toJSON(Object obj, Output out);

		public Object fromJSON(Map object);
	}

	/* ------------------------------------------------------------ */
	/**
	 * JSON Generator. A class that can add it's JSON representation directly to a StringBuffer. This is useful for object instances that are frequently converted and wish to avoid multiple Conversions
	 */
	public interface Generator
	{

		public void addJSON(Appendable buffer);
	}

	/* ------------------------------------------------------------ */
	/**
	 * A Literal JSON generator A utility instance of {@link JSON.Generator} that holds a pre-generated string on JSON text.
	 */
	public static class Literal implements Generator
	{

		private String _json;

		/* ------------------------------------------------------------ */
		/**
		 * Construct a literal JSON instance for use by {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is true, the JSON will be parsed to check validity
		 * 
		 * @param json A literal JSON string.
		 */
		public Literal(String json)
		{
			if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead!
				parse(json);
			_json = json;
		}

		@Override
		public String toString()
		{
			return _json;
		}

		public void addJSON(Appendable buffer)
		{
			try
			{
				buffer.append(_json);
			} catch (IOException e)
			{
				throw new RuntimeException(e);
			}
		}
	}
}
